/**
 * \brief 使用ffmpeg将pcm编码成mp3
 * \author 将狼才鲸
 * \date 2022-04-01
 * \note 这个case我并未测试通过，注意！因为默认ffmpeg不包含mp3编码库
 * \note 第一次编译运行会报错！注意事项：
 * 先点击“Debug”运行一次，自动创建出可执行文件输出文件夹
 * 此时“3 应用程序输出”窗口最后一行没有ffmpeg版本号“version:  3824484”输出，而只是显示了红色的异常信息
 * “程序异常结束。”，则表示没有把../ffmpeg-4.3.1-win64-shared/bin/下的*.dll动态库拷贝到
 * ../build-02_ffmpeg_pcm2mp3-Desktop_Qt_6_2_3_MinGW_64_bit-Debug/debug/下的可执行文件目录，
 * 拷贝完成后再点击“Debug”运行。
 *
 * \note 如果运行mp3编码不成功，提示Cannot open encoder，具体是avcodec_open2()函数返回-22，
 *       这是因为mp3编码库有版权，默认安装的ffmpeg不支持mp3编码，所以你需要额外装mp3编码器，
 *       还需要重新编译ffmpeg，我这里直接放弃了！我直接把改为aac。
 * \details 参考网址：
 *          [Qt搭建FFmpeg开发环境](https://blog.csdn.net/Mr_robot_strange/article/details/110292837)
 *          [FFmpeg4入门系列教程18：pcm编码为mp3](https://zhuanlan.zhihu.com/p/423450731)
 */

//#define ENCODE2AAC
#define ENCODE2MP3 // 不能找到编码器

static const char *inFileName =
        "../../004_MultiMedia_VideoAudio/002_pcm_audio_play/002_pcm_audio_play.pcm";

#ifdef ENCODE2AAC
static const char *outFileName = "test.aac";
#else
static const char *outFileName = "test.mp3";
#endif

/**
 * \brief pcm转mp3，方式一
 * \details 使用了输入输出封装结构体AVFormat和编码器AVCodec
 * \note pcm转mp3找不到编码器
 *       这份代码pcm转aac能保存文件，但是生成的aac并不能正常播放
 */
#if 0
extern "C" {
#define __STDC_CONSTANT_MACROS
#include <stdio.h>
#include "libavdevice/avdevice.h"   // 输入输出设备，输入输出文件读写
#include "libavformat/avformat.h"   // 容器封装
#include "libavformat/avio.h"
#include "libavcodec/avcodec.h"     // 解码器
#include "libavutil/imgutils.h"
#include "libswresample/swresample.h" // PCM格式转换
#include "libswscale/swscale.h"       // YUV格式转换，这里用不到
}

/**
 * \brief 清空编码器的最后一帧缓存
 */
static int flush_encoder(AVFormatContext *fmtCtx,
                         AVCodecContext *codecCtx, int aStreamIndex){
    int ret;
    AVPacket enc_pkt; // 编码后的一个包
    enc_pkt.data = NULL;
    enc_pkt.size = 0;
    av_init_packet(&enc_pkt);

    if (!(codecCtx->codec->capabilities & AV_CODEC_CAP_DELAY))
            return 0;

    printf("Flushing stream #%u encoder\n", aStreamIndex);
    // 申请清空编码器的最后一帧缓存
    if (avcodec_send_frame(codecCtx , 0) >= 0) {
        // 接收最后一帧解码完的包
        while (avcodec_receive_packet(codecCtx, &enc_pkt) >= 0) {
            printf("success encoder 1 frame.\n");
            // 写已解码包到文件
            ret = av_write_frame(fmtCtx, &enc_pkt);
            if (ret < 0) {
                break;
            }
        }
    }

    return ret;
}

int main()
{
    int ret = 0;
    AVFormatContext *fmtCtx = NULL;  // 输入输出容器封装主结构体
    AVCodecContext *codecCtx = NULL; // 编码器主结构体
    AVCodec *codec = NULL;           // 编码器
    AVFrame *frame = NULL;           // 输入帧
    AVPacket *pkt = NULL;            // 编码输出包

    /* 1. 输入输出容器封装主结构体分配内存 */
    fmtCtx = avformat_alloc_context();

    /* 2. 输入帧输出包分配内存 */
    frame = av_frame_alloc();
    pkt = av_packet_alloc();
    pkt->data = NULL;
    pkt->size = 0;

    /* 3. 查找输出封装格式，并分配输出封装内存 */
    if (avformat_alloc_output_context2(&fmtCtx, NULL, NULL, outFileName) < 0) {
        printf("Cannot alloc output file context.\n");
        return -1;
    }
    // 获取已分配好的输出封装
    AVOutputFormat *outFmt = fmtCtx->oformat;

    /* 4. 在主结构体中打开输出文件 */
    if (avio_open(&fmtCtx->pb, outFileName, AVIO_FLAG_READ_WRITE) < 0) {
        printf("Cannot open output file.\n");
        return -1;
    }

    /* 5. 在主结构体中分配输出音频流 */
    AVStream *outStream = avformat_new_stream(fmtCtx, NULL);
    if (!outStream) {
        printf("Cannot create a new stream to output file.\n");
        return -1;
    }

    /* 6. 设置输出流的编码器参数 */
    AVCodecParameters *codecPara = fmtCtx->streams[outStream->index]->codecpar;
    codecPara->codec_type = AVMEDIA_TYPE_AUDIO;
    codecPara->codec_id = outFmt->audio_codec;
    codecPara->sample_rate = 44100; // 采样率
    codecPara->channel_layout = AV_CH_LAYOUT_STEREO; // 通道类型
    codecPara->bit_rate = 64000; // 比特率
    // 编码器PCM格式，像AAC只支持FLTP，而一般麦克风输出都是S16格式，
    // 所以如果是AAC的话还需要用swresample.h转换PCM格式
    codecPara->format = AV_SAMPLE_FMT_FLTP;
    // 通道数，一般都是2
    codecPara->channels = av_get_channel_layout_nb_channels(codecPara->channel_layout);

    /* 7. 查找编码器 */
    // 按ID查找时只能找到默认的软编码器
    codec = avcodec_find_encoder(outFmt->audio_codec);
    if (codec == NULL) {
        printf("Cannot find audio encoder.\n");
        return -1;
    }

    /* 8. 分配编码器主结构体 */
    codecCtx = avcodec_alloc_context3(codec);
    avcodec_parameters_to_context(codecCtx, codecPara);
    if(codecCtx == NULL){
        printf("Cannot alloc codec ctx from para.\n");
        return -1;
    }

    /* 9. 打开编码器 */
    ret = avcodec_open2(codecCtx, codec, NULL);
    if (ret < 0) {
        printf("Cannot open encoder. %d\n", ret);
        return -1;
    }

    // 打印输出容器封装信息
    //av_dump_format(fmtCtx, 0, outFileName, 1);

    // 输出帧参数设置
    frame->nb_samples = codecCtx->frame_size; // 每帧采样点，一般默认1024
    frame->format = codecCtx->sample_fmt; // 采样点bit深度
    frame->channels = 2; // 通道数

    /* 10. PCM S16转FLTP，PCM重采样 */
    struct SwrContext *swrCtx = swr_alloc_set_opts(NULL,
                       av_get_default_channel_layout(codecCtx->channels),
                       codecCtx->sample_fmt, // FLTP
                       codecCtx->sample_rate,
                       av_get_default_channel_layout(frame->channels),
                       AV_SAMPLE_FMT_S16, // PCM源文件的采样格式
                       44100, 0, NULL);
    swr_init(swrCtx);

    // 为PCM转换分配空间
    uint8_t **convert_data = (uint8_t **)calloc(codecCtx->channels, sizeof(*convert_data));
    av_samples_alloc(convert_data, NULL, codecCtx->channels,
                     codecCtx->frame_size,
                     codecCtx->sample_fmt, 0);

    int size = av_samples_get_buffer_size(NULL, codecCtx->channels,
                                          codecCtx->frame_size, codecCtx->sample_fmt, 1);
    uint8_t *frameBuf = (uint8_t*)av_malloc(size);
    avcodec_fill_audio_frame(frame, codecCtx->channels, codecCtx->sample_fmt,
                             (const uint8_t*)frameBuf, size, 1);

    /* 11. 将容器头信息写到输出文件 */
    // aac有头信息，mp3可以没有
    ret = avformat_write_header(fmtCtx, NULL);

    FILE *inFile = fopen(inFileName, "rb");
    if (!inFile) {
        printf("Cannot open input file.\n");
        return -1;
    }

    /* 循环编码每一帧 */
    for (int i=0; ; i++) {
        // 输入一帧数据的长度
        int length = frame->nb_samples * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) * frame->channels;
        // 读一帧PCM
        if(fread(frameBuf, 1, length, inFile) <= 0) {
            printf("Cannot read raw data from file.\n");
            return -1;
        } else if (feof(inFile)) {
            // 如果输入文件结束
            break;
        }

        // YUV S16转FLTP
        swr_convert(swrCtx, convert_data, codecCtx->frame_size,
                    (const uint8_t**)frame->data,
                    frame->nb_samples);

        //输出一帧数据的长度
        length = codecCtx->frame_size * av_get_bytes_per_sample(codecCtx->sample_fmt);
        //双通道赋值（输出的AAC为双通道）
        memcpy(frame->data[0],convert_data[0],length);
        memcpy(frame->data[1],convert_data[1],length);

        frame->pts = i*100;
        if (avcodec_send_frame(codecCtx,frame) < 0) {
            while(avcodec_receive_packet(codecCtx,pkt) >= 0) {
                pkt->stream_index = outStream->index;
                printf("write %4d frame, size=%d, length=%d\n", i, size, length);
                av_write_frame(fmtCtx,pkt);
            }
        }
        av_packet_unref(pkt);
    }

    // flush encoder
    if (flush_encoder(fmtCtx, codecCtx, outStream->index) < 0) {
        printf("Cannot flush encoder.\n");
        return -1;
    }

    // write trailer
    av_write_trailer(fmtCtx);

    fclose(inFile);
    av_free(frameBuf);
    avcodec_close(codecCtx);
    av_free(frame);
    avio_close(fmtCtx->pb);
    avformat_free_context(fmtCtx);

    return 0;
}
#endif

/**
 * \brief pcm转mp3，方式二
 * \details 只使用编码器AVCodec，不使用输入输出封装结构体AVFormat
 * \note pcm转mp3找不到编码器
 */
#if 1 // av_frame_get_buffer failed，还有别的各种报错
#include "mainwindow.h"
#include <QApplication>

extern "C" {
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <libavcodec/avcodec.h>
    #include <libavutil/opt.h>
    #include <libavutil/imgutils.h>
}

static void encode_audio(const char *input_file, const char *output_file);
int main(int argc, char *argv[])
{
//    QApplication a(argc, argv);
//    MainWindow w;
//    w.show();
//    return a.exec();
    Q_UNUSED(argc);
    Q_UNUSED(argv);

    /* 1. 测试输出FFmpeg版本测试环境部署是否成功 */
    avcodec_configuration();
    unsigned version = avcodec_version();
    QString ch = QString::number(version,10);
    qDebug()<<"version: "<<version;

    // Uage:<input file> <output file>
    encode_audio(inFileName, outFileName);

    return 0;
}

// 每次解码一帧
static void encode(AVCodecContext *cdc_ctx, AVFrame *frame, AVPacket *pkt, FILE *fp_out)
{
    int ret = 0;

    if ((ret = avcodec_send_frame(cdc_ctx, frame)) < 0)
    {
        fprintf(stderr, "avcodec_send_frame failed.\n");
        exit(1);
    }

    while ((ret = avcodec_receive_packet(cdc_ctx, pkt)) >= 0)
    {
        printf("Write (size=%d) packet.\n", pkt->size);
        // 写编码完的一包到输出文件
        fwrite(pkt->data, 1, pkt->size, fp_out);
        av_packet_unref(pkt);
    }

    if ((ret != AVERROR(EAGAIN)) && (ret != AVERROR_EOF))
    {
        fprintf(stderr, "avcodec_receive_packet failed.\n");
        exit(1);
    }
}

// PCM编码主入口
void encode_audio(const char *input_file, const char *output_file)
{
    int ret = 0;
    int data_size = 0;
    AVCodec *codec = NULL;
    AVCodecContext *cdc_ctx = NULL;
    AVPacket *pkt = NULL;
    AVFrame *frame = NULL;
    FILE *fp_in, *fp_out;

    // 不使用AVFormatContext输入输出容器封装，
    // 也不写输出容器的头信息和尾信息，
    // 用户进行AAC、MP4等容器封装时需要自行把编码器中的各种信息合成容器头信息

#ifdef ENCODE2AAC
    if ((codec = avcodec_find_encoder(AV_CODEC_ID_AAC)) == NULL)
#else
    if ((codec = avcodec_find_encoder(AV_CODEC_ID_MP3)) == NULL)
#endif
    {
        fprintf(stderr, "avcodec_find_encoder_by_name failed.\n");
        goto ret1;
    }

    if ((cdc_ctx = avcodec_alloc_context3(codec)) == NULL)
    {
        fprintf(stderr, "avcodec_alloc_context3 failed.\n");
        goto ret1;
    }

    // 原始文件信息：ffplay -ar 44100 -ac 2 -f s16le -i output.pcm
    // 这里使用PCM FLTP，所以还需要添加重采样进行格式转换
    // AAC编码器只支持FLTP
    cdc_ctx->bit_rate = 192000;
    cdc_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
    cdc_ctx->sample_rate = 44100;
    cdc_ctx->channel_layout = av_get_channel_layout("stereo");
    cdc_ctx->channels = av_get_channel_layout_nb_channels(cdc_ctx->channel_layout);

    if ((ret = avcodec_open2(cdc_ctx, codec, NULL)) < 0)
    {
        fprintf(stderr, "avcodec_open2 failed.\n");
        goto ret2;
    }

    if ((pkt = av_packet_alloc()) == NULL)
    {
        fprintf(stderr, "av_packet_alloc failed.\n");
        goto ret3;
    }

    if ((frame = av_frame_alloc()) == NULL)
    {
        fprintf(stderr, "av_frame_alloc failed.\n");
        goto ret4;
    }
    frame->nb_samples = cdc_ctx->frame_size;
    frame->format = cdc_ctx->sample_fmt;
    frame->channel_layout = cdc_ctx->channel_layout;

    if ((ret = av_frame_get_buffer(frame, 0)) < 0)
    {
        fprintf(stderr, "av_frame_get_buffer failed.\n");
        goto ret5;
    }

    if ((fp_in = fopen(input_file, "rb")) == NULL)
    {
        fprintf(stderr, "fopen %s failed.\n", input_file);
        goto ret5;
    }
    if ((fp_out = fopen(output_file, "wb")) == NULL)
    {
        fprintf(stderr, "fopen %s failed.\n", output_file);
        goto ret6;
    }

    /* cdc_ctx->sample_fmt = AV_SAMPLE_FMT_S16P */
    data_size = av_samples_get_buffer_size(NULL, cdc_ctx->channels, cdc_ctx->frame_size, cdc_ctx->sample_fmt, 1);
    printf("data_size = %d\n", data_size);
    while (feof(fp_in) == 0)
    {
        if ((ret = av_frame_make_writable(frame)) < 0)
        {
            fprintf(stderr, "frame is not writable.\n");
            goto ret7;
        }

        fread(frame->data[0], 1, data_size, fp_in);
        encode(cdc_ctx, frame, pkt, fp_out);
    }

    encode(cdc_ctx, NULL, pkt, fp_out);

    fclose(fp_out);
    fclose(fp_in);
    av_frame_free(&frame);
    av_packet_free(&pkt);
    avcodec_close(cdc_ctx);
    avcodec_free_context(&cdc_ctx);
    return;
ret7:
    fclose(fp_out);
ret6:
    fclose(fp_in);
ret5:
    av_frame_free(&frame);
ret4:
    av_packet_free(&pkt);
ret3:
    avcodec_close(cdc_ctx);
ret2:
    avcodec_free_context(&cdc_ctx);
ret1:
    exit(1);
}
#endif
